﻿// 2022/4/1: 首个版本
// 2022/4/2: 增加TransformID
// 2022/5/11: 增加MenuConfigItemType.Task
// 2022/5/11: 增加MenuConfigItem.Title
// 2023/6/19: 增加MenuConfig.PythonTemplates
// 2023/11/22: 增加MenuConfigItemType.Console
// 2024/3/1: 支持preset
// 2024/6/25: 不使用Agency
// 2024/6/26: 使用Language枚举，输出语言代号zh

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Linq;
using ASEva;
using SO = System.Reflection.ObfuscationAttribute;

namespace CommonSource
{
    class MenuConfig
    {
        public static String MenuConfigFilePath
        {
            get
            {
                return AgencyLocal.GetConfigFilesRoot() + Path.DirectorySeparatorChar + "menu.xml";
            }
        }

        public List<MenuConfigItem> Items { get; set; }
        public List<MenuExplicitItem> Plugins { get; set; }
        public List<MenuExplicitItem> HTMLModules { get; set; }
        public List<MenuExplicitItem> PythonTemplates { get; set; }
        public List<MenuExplicitItem> Documents { get; set; }
        public Dictionary<String, Dictionary<Language, String>> PresetTitles { get; set; }

        public String[] InvalidPresets { get; set; }

        private MenuConfig()
        {
            Items = new List<MenuConfigItem>();
            Plugins = new List<MenuExplicitItem>();
            HTMLModules = new List<MenuExplicitItem>();
            PythonTemplates = new List<MenuExplicitItem>();
            Documents = new List<MenuExplicitItem>();
            PresetTitles = new Dictionary<string, Dictionary<Language, string>>();
            InvalidPresets = new String[0];
        }

        public static MenuConfig Create(List<MenuConfigItem> items)
        {
            var output = new MenuConfig();
            if (items != null) output.Items = items;
            return output;
        }

        public static MenuConfig LoadXmlFile(String filePath)
        {
            if (!File.Exists(filePath)) return null;

            var xml = new XmlDocument();
            try
            {
                xml.Load(filePath);
                return load(xml);
            }
            catch (Exception) { return null; }
        }

        public static MenuConfig LoadModuleConfigFile(String filePath)
        {
            if (!File.Exists(filePath)) return null;

            var xml = new XmlDocument();
            try { xml.Load(filePath); }
            catch (Exception) { return null; }

            var rootNode = xml.DocumentElement;
            var typeName = rootNode.Attributes["type"].Value.ToLower();

            if (typeName == "menu-config-v1")
            {
                var innerXmlText = Encoding.UTF8.GetString(Convert.FromBase64String(xml.DocumentElement.InnerText));
                var innerXml = new XmlDocument();
                try { innerXml.LoadXml(innerXmlText); }
                catch (Exception) { return null; }
                xml = innerXml;
            }
            else if (typeName != "menu-config-v2") return null;

            try { return load(xml); }
            catch (Exception) { return null; }
        }

        public static MenuConfig LoadString(String xmlString)
        {
            if (String.IsNullOrEmpty(xmlString)) return null;

            var xml = new XmlDocument();
            try
            {
                xml.LoadXml(xmlString);
                return load(xml);
            }
            catch (Exception) { return null; }
        }

        public MenuConfig Clone()
        {
            var output = MenuConfig.Create(MenuConfigItem.Clone(Items));
            output.Plugins = Plugins.ToArray().ToList();
            output.HTMLModules = HTMLModules.ToArray().ToList();
            output.PythonTemplates = PythonTemplates.ToArray().ToList();
            output.Documents = Documents.ToArray().ToList();
            return output;
        }

        public static bool MenuEquals(MenuConfig menu1, MenuConfig menu2)
        {
            if (menu1 == null) menu1 = MenuConfig.Create(null);
            if (menu2 == null) menu2 = MenuConfig.Create(null);
            if (!MenuConfigItem.MenuEquals(menu1.Items, menu2.Items)) return false;

            if (menu1.Plugins.Count != menu2.Plugins.Count) return false;
            for (int i = 0; i < menu1.Plugins.Count; i++)
            {
                if (menu1.Plugins[i] != menu2.Plugins[i]) return false;
            }

            if (menu1.HTMLModules.Count != menu2.HTMLModules.Count) return false;
            for (int i = 0; i < menu1.HTMLModules.Count; i++)
            {
                if (menu1.HTMLModules[i] != menu2.HTMLModules[i]) return false;
            }

            if (menu1.PythonTemplates.Count != menu2.PythonTemplates.Count) return false;
            for (int i = 0; i < menu1.PythonTemplates.Count; i++)
            {
                if (menu1.PythonTemplates[i] != menu2.PythonTemplates[i]) return false;
            }

            if (menu1.Documents.Count != menu2.Documents.Count) return false;
            for (int i = 0; i < menu1.Documents.Count; i++)
            {
                if (menu1.Documents[i] != menu2.Documents[i]) return false;
            }

            return true;
        }

        public void Save(String filePath, bool asModuleConfigFile)
        {
            if (Items == null) Items = new List<MenuConfigItem>();
            if (Plugins == null) Plugins = new List<MenuExplicitItem>();
            if (HTMLModules == null) HTMLModules = new List<MenuExplicitItem>();
            if (PythonTemplates == null) PythonTemplates = new List<MenuExplicitItem>();
            if (Documents == null) Documents = new List<MenuExplicitItem>();

            var xml = new XmlDocument();
            xml.AppendChild(xml.CreateXmlDeclaration("1.0", "utf-8", null));
            xml.AppendChild(xml.CreateElement("root"));
            if (asModuleConfigFile) xml.DocumentElement.Attributes.Append(xml.CreateAttribute("type")).Value = "menu-config-v2";

            foreach (var pair in PresetTitles)
            {
                var presetNode = xml.DocumentElement.AppendChild(xml.CreateElement("preset")) as XmlElement;
                presetNode.Attributes.Append(xml.CreateAttribute("id")).Value = pair.Key;
                foreach (var titlePair in pair.Value)
                {
                    String langID = titlePair.Key switch
                    {
                        Language.English => "en",
                        Language.Chinese => "zh",
                        _ => null,
                    };
                    if (langID == null) continue;

                    var titleNode = presetNode.AppendChild(xml.CreateElement("title")) as XmlElement;
                    titleNode.Attributes.Append(xml.CreateAttribute("lang")).Value = langID;
                    titleNode.InnerText = titlePair.Value;
                }
            }

            foreach (var item in Items)
            {
                recItem(xml.DocumentElement.AppendChild(xml.CreateElement("item")) as XmlElement, item, false);
            }
            foreach (var item in Plugins)
            {
                recItem(xml.DocumentElement.AppendChild(xml.CreateElement("plugin")) as XmlElement, item);
            }
            foreach (var item in HTMLModules)
            {
                recItem(xml.DocumentElement.AppendChild(xml.CreateElement("html")) as XmlElement, item);
            }
            foreach (var item in PythonTemplates)
            {
                recItem(xml.DocumentElement.AppendChild(xml.CreateElement("python")) as XmlElement, item);
            }
            foreach (var item in Documents)
            {
                recItem(xml.DocumentElement.AppendChild(xml.CreateElement("doc")) as XmlElement, item);
            }

            xml.Save(filePath);
        }

        private void recItem(XmlElement node, MenuConfigItem item, bool isSub)
        {
            if (item.Type == MenuConfigItemType.Invalid) return;
            if (isSub && item.Type == MenuConfigItemType.Parent) return;

            var xml = node.OwnerDocument;
            var attribs = node.Attributes;

            if (item.Presets != null && item.Presets.Length > 0) attribs.Append(xml.CreateAttribute("presets")).Value = String.Join(',', item.Presets);

            var type = item.Type;
            attribs.Append(xml.CreateAttribute("type")).Value = type.ToString();

            if (type == MenuConfigItemType.System || type == MenuConfigItemType.Window || type == MenuConfigItemType.Dialog || type == MenuConfigItemType.Parent || type == MenuConfigItemType.Task || type == MenuConfigItemType.Console)
            {
                attribs.Append(xml.CreateAttribute("id")).Value = item.ID;
            }

            if (type == MenuConfigItemType.Window || type == MenuConfigItemType.Dialog || type == MenuConfigItemType.Task)
            {
                var config = item.Config == null ? "" : item.Config;
                node.InnerText = Convert.ToBase64String(Encoding.UTF8.GetBytes(config));
            }

            if (type == MenuConfigItemType.Window || type == MenuConfigItemType.Dialog || type == MenuConfigItemType.Task || type == MenuConfigItemType.Console)
            {
                attribs.Append(xml.CreateAttribute("online")).Value = item.Online ? "yes" : "no";
            }

            if (type == MenuConfigItemType.Window || type == MenuConfigItemType.Dialog)
            {
                var transformID = item.TransformID == null ? "" : item.TransformID;
                attribs.Append(xml.CreateAttribute("transform_id")).Value = transformID;
            }

            if (!isSub && type == MenuConfigItemType.Parent)
            {
                if (item.Children != null)
                {
                    foreach (var subItem in item.Children)
                    {
                        if (subItem.Type == MenuConfigItemType.Invalid || subItem.Type == MenuConfigItemType.Parent) continue;
                        recItem(node.AppendChild(node.OwnerDocument.CreateElement("sub-item")) as XmlElement, subItem, true);
                    }
                }
            }

            if (type == MenuConfigItemType.System)
            {
                if (item.Title != null)
                {
                    foreach (var pair in item.Title)
                    {
                        String langID = pair.Key switch
                        {
                            Language.English => "en",
                            Language.Chinese => "zh",
                            _ => null,
                        };
                        if (langID == null) continue;

                        var elem = node.AppendChild(node.OwnerDocument.CreateElement("title")) as XmlElement;
                        elem.Attributes.Append(node.OwnerDocument.CreateAttribute("lang")).Value = langID;
                        elem.InnerText = pair.Value;
                    }
                }
            }
        }

        private void recItem(XmlElement node, MenuExplicitItem item)
        {
            node.InnerText = item.ID;
            if (item.Presets != null && item.Presets.Length > 0) node.Attributes.Append(node.OwnerDocument.CreateAttribute("presets")).Value = String.Join(',', item.Presets);
        }

        private static MenuConfig load(XmlDocument xml)
        {
            var output = new MenuConfig();

            foreach (XmlElement presetNode in xml.DocumentElement.GetElementsByTagName("preset"))
            {
                var id = presetNode.Attributes["id"].Value;
                var titles = new Dictionary<Language, String>();
                foreach (XmlElement titleNode in presetNode.GetElementsByTagName("title"))
                {
                    var langID = titleNode.Attributes["lang"].Value;
                    var language = langID switch
                    {
                        "en" => Language.English,
                        "zh" => Language.Chinese,
                        "ch" => Language.Chinese,
                        _ => Language.Invalid,
                    };
                    if (language != Language.Invalid) titles[language] = titleNode.InnerText;
                }
                output.PresetTitles[id] = titles;
            }

            var presets = output.PresetTitles.Keys.ToList();
            var invalidPresetFlags = new Dictionary<String, bool>();

            var list = new List<MenuConfigItem>();
            foreach (XmlElement mainNode in xml.DocumentElement.GetElementsByTagName("item"))
            {
                var item = loadItem(mainNode, false, presets, invalidPresetFlags);
                if (item != null) list.Add(item);
            }
            output.Items = list;

            foreach (XmlElement pluginNode in xml.DocumentElement.GetElementsByTagName("plugin"))
            {
                var item = loadExplicitItem(pluginNode, presets, invalidPresetFlags);
                output.Plugins.Add(item);
            }
            foreach (XmlElement htmlNode in xml.DocumentElement.GetElementsByTagName("html"))
            {
                var item = loadExplicitItem(htmlNode, presets, invalidPresetFlags);
                output.HTMLModules.Add(item);
            }
            foreach (XmlElement pythonNode in xml.DocumentElement.GetElementsByTagName("python"))
            {
                var item = loadExplicitItem(pythonNode, presets, invalidPresetFlags);
                output.PythonTemplates.Add(item);
            }
            foreach (XmlElement docNode in xml.DocumentElement.GetElementsByTagName("doc"))
            {
                var item = loadExplicitItem(docNode, presets, invalidPresetFlags);
                output.Documents.Add(item);
            }

            output.InvalidPresets = invalidPresetFlags.Keys.ToArray();
            return output;
        }

        private static MenuConfigItem loadItem(XmlElement node, bool isSub, List<String> presets, Dictionary<String, bool> invalidPresetFlags)
        {
            var attribs = node.Attributes;
            MenuConfigItemType type = MenuConfigItemType.Invalid;
            if (attribs["type"] == null || !Enum.TryParse<MenuConfigItemType>(attribs["type"].Value, out type)) return null;
            if (type == MenuConfigItemType.Invalid) return null;
            if (isSub && type == MenuConfigItemType.Parent) return null;

            var item = new MenuConfigItem();
            item.Type = type;

            if (attribs["presets"] != null)
            {
                item.Presets = attribs["presets"].Value.Split(',');
                foreach (var preset in item.Presets)
                {
                    if (!presets.Contains(preset)) invalidPresetFlags[preset] = true;
                }
            }

            if (type == MenuConfigItemType.System || type == MenuConfigItemType.Window || type == MenuConfigItemType.Dialog || type == MenuConfigItemType.Parent || type == MenuConfigItemType.Task || type == MenuConfigItemType.Console)
            {
                item.ID = attribs["id"] == null ? null : attribs["id"].Value;
                if (String.IsNullOrEmpty(item.ID)) return null;
            }

            if (type == MenuConfigItemType.Window || type == MenuConfigItemType.Dialog || type == MenuConfigItemType.Task)
            {
                item.Config = Encoding.UTF8.GetString(Convert.FromBase64String(node.InnerText));
            }

            if (type == MenuConfigItemType.Window || type == MenuConfigItemType.Dialog || type == MenuConfigItemType.Task || type == MenuConfigItemType.Console)
            {
                item.Online = attribs["online"] == null ? false : attribs["online"].Value == "yes";
            }

            if (type == MenuConfigItemType.Window || type == MenuConfigItemType.Dialog)
            {
                item.TransformID = attribs["transform_id"] == null ? "" : attribs["transform_id"].Value;
            }

            if (!isSub && type == MenuConfigItemType.Parent)
            {
                var list = new List<MenuConfigItem>();
                foreach (XmlElement subNode in node.GetElementsByTagName("sub-item"))
                {
                    var subItem = loadItem(subNode, true, presets, invalidPresetFlags);
                    if (subItem != null && subItem.Type != MenuConfigItemType.Invalid && subItem.Type != MenuConfigItemType.Parent) list.Add(subItem);
                }
                item.Children = list;
            }

            if (type == MenuConfigItemType.System)
            {
                var title = new Dictionary<Language, String>();
                foreach (XmlElement subNode in node.GetElementsByTagName("title"))
                {
                    if (subNode.Attributes["lang"] == null || subNode.Attributes["lang"].Value.Length == 0 || subNode.InnerText.Length == 0) continue;
                    var language = subNode.Attributes["lang"].Value switch
                    {
                        "en" => Language.English,
                        "zh" => Language.Chinese,
                        "ch" => Language.Chinese,
                        _ => Language.Invalid,
                    };
                    if (language != Language.Invalid) title[language] = subNode.InnerText;
                }
                item.Title = title;
            }

            return item;
        }

        private static MenuExplicitItem loadExplicitItem(XmlElement node, List<String> presets, Dictionary<String, bool> invalidPresetFlags)
        {
            var item = new MenuExplicitItem();
            item.ID = node.InnerText;
            if (node.Attributes["presets"] != null)
            {
                item.Presets = node.Attributes["presets"].Value.Split(',');
                foreach (var preset in item.Presets)
                {
                    if (!presets.Contains(preset)) invalidPresetFlags[preset] = true;
                }
            }
            return item;
        }
    }

    [SO]
    enum MenuConfigItemType
    {
        Invalid,
        Parent, // ID, Children
        System, // ID, Title
        Window, // ID, Config, Online, TransformID
        Dialog, // ID, Config, Online, TransformID
        Splitter,
        Task, // ID, Config, Online
        Console, // ID, Online
    }

    class MenuConfigItem
    {
        public String[] Presets { get; set; }
        public MenuConfigItemType Type { get; set; }
        public String ID { get; set; }
        public String TransformID { get; set; }
        public String Config { get; set; }
        public bool Online { get; set; }
        public List<MenuConfigItem> Children { get; set; }
        public Dictionary<Language, String> Title { get; set; }

        public MenuConfigItem Clone()
        {
            var item = new MenuConfigItem();
            item.Type = Type;
            item.ID = ID;
            item.TransformID = TransformID;
            item.Config = Config;
            item.Online = Online;
            if (Children != null)
            {
                item.Children = new List<MenuConfigItem>();
                foreach (var subItem in Children)
                {
                    item.Children.Add(subItem.Clone());
                }
            }
            if (Title != null)
            {
                item.Title = new Dictionary<Language, String>();
                foreach (var pair in Title)
                {
                    item.Title[pair.Key] = pair.Value;
                }
            }
            return item;
        }

        public bool SameAs(MenuConfigItem item)
        {
            if (item == null) return false;
            if (Type != item.Type) return false;
            if (ID != item.ID) return false;
            if (Config != item.Config) return false;
            if (Online != item.Online) return false;
            if (TransformID != item.TransformID) return false;
            if (Children == null || Children.Count == 0)
            {
                if (item.Children != null && item.Children.Count > 0) return false;
            }
            else
            {
                if (item.Children == null || item.Children.Count != Children.Count) return false;
                for (int i = 0; i < Children.Count; i++)
                {
                    if (!item.Children[i].SameAs(Children[i])) return false;
                }
            }
            if (Title == null || Title.Count == 0)
            {
                if (item.Title != null && item.Title.Count > 0) return false;
            }
            else
            {
                if (item.Title == null || item.Title.Count != Title.Count) return false;
                foreach (var pair in Title)
                {
                    if (!item.Title.ContainsKey(pair.Key))return false;
                    if (item.Title[pair.Key] != pair.Value) return false;
                }
            }
            return true;
        }

        public static List<MenuConfigItem> Clone(List<MenuConfigItem> menu)
        {
            if (menu == null) return null;
            var output = new List<MenuConfigItem>();
            foreach (var item in menu)
            {
                output.Add(item.Clone());
            }
            return output;
        }

        public static bool MenuEquals(List<MenuConfigItem> menu1, List<MenuConfigItem> menu2)
        {
            if (menu1 == null) menu1 = new List<MenuConfigItem>();
            if (menu2 == null) menu2 = new List<MenuConfigItem>();
            if (menu1.Count != menu2.Count) return false;
            for (int i = 0; i < menu1.Count; i++)
            {
                if (!menu1[i].SameAs(menu2[i])) return false;
            }
            return true;
        }
    }

    class MenuExplicitItem
    {
        public String ID { get; set; }
        public String[] Presets { get; set; }
    }
}
